"""
Created on 26-Feb-2010

@author: dexen
"""

import time
import threading

import rpyc

from dexen.system.server.naming_manager import NamingManager
from dexen.system.server.info import SlaveInfo, MasterInfo
from dexen.system.server.proxy import NodeProxy, MasterProxy

#===============================================================================
# Constants
#===============================================================================

INF = 100000   
    
#===============================================================================
# 
#===============================================================================
class JobManager(threading.Thread):
    def __init__(self, reg_addr):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.name = "JobManager"    
        self.nodes = {} # proxies    
        self.masters = {} # proxies

        self.slaves = {} # infos
        
        self.master_namer = NamingManager("MASTER")    
        self.slave_namer = NamingManager("SLAVE")            

        """ The ip and port address of node regitry """
        self.reg_addr = reg_addr

        self.lock = threading.RLock()

    def _update_masters(self):
        for master in self.masters.values():
            assert isinstance(master, MasterProxy)
            master.update()
            
           
            if master.is_crashed():
                """ DO SOMETHING """
                continue
            
            free_slave = None
            
            """
                Trying to reassign a slave to the master
                Note that the slaves_info is coming from master and they replace
                the old slave_info to update the state.
                Note that the slaves will be freed in the master
            """
            slaves_info = master.get_slaves_info()
            for slave in slaves_info:
                assert isinstance(slave, SlaveInfo)
                self.slaves[slave.name] = slave
                if slave.is_idle():
                    print slave.name, "has become idle", time.time()
                    free_slave = slave  
                else:
                    # It must be crashed
                    print slave.name, "has crashed", time.time()
                    pass
    
    
            if not master.has_pending_task():
                continue 
    
            """
                If none of the slave has become free yet,
                tries to executing other slaves
            """
            if not free_slave:
                for slave in self.slaves.values():
                    if slave.is_idle():
                        free_slave = slave
                        break
                    
            """
               If none of the slaves is free, then move on to the next master 
            """
            if not free_slave:
                continue
            
            assert isinstance(free_slave, SlaveInfo)
            free_slave.executing() # This is important
            print free_slave.name, "is assigned", time.time()
            master.schedule(free_slave.name)
            
            
    def run(self):
        """
            This is the starting point of job manager thread runs in parallel 
            with node manager server thread that serves clients to submit jobs,
            etc..
        """
        self.node_reg = rpyc.connect(self.reg_addr[0], self.reg_addr[1])
        while True:
            with self.lock:
                self._update_nodes()
                self._update_masters()
            time.sleep(0.1)
    
    def _create_slaves(self, name):
        node = self.nodes[name]
        assert isinstance(node, NodeProxy)
        for _ in range(node.max_slots-1):
            name = self.slave_namer.get_name()
            self.slaves[name] = node.create_slave(name)
            
    def _update_nodes(self):
        """ 
            The new_nodes and dead_nodes are of type dict.
            key: the name of the nodes
            value: the timestamp of the creation of node
        """
        new_nodes, dead_nodes = self.node_reg.root.get_nodes()
        new_nodes = rpyc.classic.obtain(new_nodes)
        dead_nodes = rpyc.classic.obtain(dead_nodes)
        
        for name in dead_nodes.keys():
            node = self.nodes[name]
            assert isinstance(node, NodeProxy)
            node.dead()
            """
            TODO: If the node is dead, check the slaves belonging the node,
            to find out if the machine is down.
            """
            
        for name in new_nodes.keys():
            addr = new_nodes[name][1]
            self.nodes[name] = NodeProxy(name, addr, self.reg_addr)
            self._create_slaves(name)

    def _get_free_node(self):
        """
        For creating master
        TODO: Handle the case when all the slots are taken
        """
        for node in self.nodes.values():
            if node.has_master_slot() and node.is_alive():
                return node              
              
    def run_master(self, spec):
        """
        gonna be called by node manager when the user wants to run a job
        """
        with self.lock:
            name = self.master_namer.get_name()
            node = self._get_free_node()
            master = node.create_master(name)
            assert isinstance(master, MasterProxy)
            master.run_schema(spec)
            self.masters[name] = master
 
    def pause_master(self, name):
        pass
    
    def stop_master(self, name): 
        pass

    def __repr__(self):
        t = ""
        for name, node in self.nodes.items():
            t += "%s:\n%r\n"%(name, node)
        return t
    